/**
 *  Copyright 2013 University Pierre & Marie Curie - UMR CNRS 7606 (LIP6/MoVe)
 *  All rights reserved.   This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  which accompanies this distribution, and is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  Initial contributor:
 *    Jean-Luc Amitousa - Thanh Cong - <jeanluc.amitousa.mankoy@gmail.com>
 *
 *  Mailing list:
 *    lom-messan.hillah@lip6.fr
 */
package sdmakerz_v1.coresyntax.validation;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.validation.Check;

import sdmakerz_v1.coresyntax.SDMakerV1Utils;
import sdmakerz_v1.coresyntax.io.RefConflictException;
import sdmakerz_v1.coresyntax.io.RefNotFoundException;
import sdmakerz_v1.coresyntax.io.SDMakerIO;
import sdmakerz_v1.coresyntax.sDMakerV1.Binding;
import sdmakerz_v1.coresyntax.sDMakerV1.Config;
import sdmakerz_v1.coresyntax.sDMakerV1.Diagram;
import sdmakerz_v1.coresyntax.sDMakerV1.DiagramElement;
import sdmakerz_v1.coresyntax.sDMakerV1.DiagramRef;
import sdmakerz_v1.coresyntax.sDMakerV1.Eval;
import sdmakerz_v1.coresyntax.sDMakerV1.ImportType;
import sdmakerz_v1.coresyntax.sDMakerV1.Lifeline;
import sdmakerz_v1.coresyntax.sDMakerV1.Obj;
import sdmakerz_v1.coresyntax.sDMakerV1.Reference;
import sdmakerz_v1.coresyntax.sDMakerV1.SDMakerModel;
import sdmakerz_v1.coresyntax.sDMakerV1.SDMakerV1Package;
import sdmakerz_v1.coresyntax.sDMakerV1.Type;


public class SDMakerV1JavaValidator extends AbstractSDMakerV1JavaValidator {

	@Check
	public void checkUniqLifelineName(Lifeline l){
		EObject container= SDMakerV1Utils.
				findContainer(l, Diagram.class);
		Diagram d= (Diagram)container;

		if(d==null){
			String error= "Internal error: no containing Diagram found";
			error(error, SDMakerV1Package.Literals.LIFELINE__NAME);
		}

		for(DiagramElement e : d.getDiagram_elts()){
			if((e instanceof Lifeline)&&(e!=l)){
				Lifeline tmp= (Lifeline)e;

				if(l.getName().compareTo(tmp.getName())==0){
					String strError= "";

					strError+= "name \""+l.getName()+"\" must be uniq in diagram.";
					error(strError, SDMakerV1Package.Literals.LIFELINE__NAME);
				}
			}
		}
	}

	@Check
	public void checkExistingType(Type type){
		EObject container= SDMakerV1Utils.findContainer(type, SDMakerModel.class);
		List<String> matchingImports= null;

		if(container==null){
			String error= "Internal error: no containing Model found";
			error(error, SDMakerV1Package.Literals.TYPE__IDENTIFIER);
		}

		if(container instanceof SDMakerModel){
			/*
			 * Contrairement aux SDEditModel, les types d'un SDMakerModel
			 * doivent etres importes.
			 */
			matchingImports= 
					SDMakerV1Utils
					.findMatchingQualifiedNames(
							type.eResource(), type.getIdentifier());

			if(matchingImports.size()==0){
				String strError= type.getIdentifier()+" is not a Type.";
				error(strError, SDMakerV1Package.Literals.TYPE__IDENTIFIER);
			}
		}
	}

	@Check
	public void checkTypeImportsConflicts(ImportType imp){
		EObject container= 
				SDMakerV1Utils.findContainer(imp, SDMakerModel.class);
		List<String> matchingImports= new ArrayList<String>();
		TreeIterator<EObject> modelContent= null;

		if(container==null){
			String error= "Internal error: no containing SDMakerModel found";
			error(error, SDMakerV1Package.Literals.IMPORT__NAME);
		}

		modelContent= ((SDMakerModel)container).eAllContents();

		while(modelContent.hasNext()){
			EObject eo= modelContent.next();

			if(eo instanceof ImportType){
				ImportType impTmp= (ImportType)eo;
				String simpleName= 
						SDMakerV1Utils.computeSimpleName(imp.getName());
				String simpleNameTmp= 
						SDMakerV1Utils.computeSimpleName(impTmp.getName());
				if(simpleName.compareTo(simpleNameTmp)==0){
					matchingImports.add(impTmp.getName());
				}
			}
		}

		if(matchingImports.size() > 1){
			String strError= "conflicting type imports: ";

			for(int i= 0; i < (matchingImports.size()-1); i++){
				strError += matchingImports.get(i)+", ";
			}

			strError += matchingImports.get(matchingImports.size()-1)+" .";
			error(strError, SDMakerV1Package.Literals.IMPORT__NAME);
		}
	}

	@Check
	public void checkUniqDiagramNameInFile(Diagram diag){
		EObject container= 
				SDMakerV1Utils.findContainer(diag, SDMakerModel.class);
		TreeIterator<EObject> modelContent= null;
		int nbTimes= 0;

		if(container==null){
			String error= "Internal error: no containing SDMakerModel found";
			error(error, SDMakerV1Package.Literals.TYPE__IDENTIFIER);
		}

		modelContent= ((SDMakerModel)container).eAllContents();

		while(modelContent.hasNext()){
			EObject eo= modelContent.next();

			if(eo instanceof Diagram){
				Diagram dTmp= (Diagram)eo;

				if(diag.getName().compareTo(dTmp.getName())==0){
					nbTimes++;
				}
			}
		}

		if(nbTimes > 1){
			String error= "diagram \""+diag.getName()+"\" have to be uniq";
			error(error, SDMakerV1Package.Literals.DIAGRAM__NAME);
		}
	}

	@Check
	public void checkUniqConfigNameInFile(Config config){
		EObject container= 
				SDMakerV1Utils.findContainer(config, SDMakerModel.class);
		TreeIterator<EObject> modelContent= null;
		int nbTimes= 0;

		if(container==null){
			String error= "Internal error: no containing SDMakerModel found";
			error(error, SDMakerV1Package.Literals.TYPE__IDENTIFIER);
		}

		modelContent= ((SDMakerModel)container).eAllContents();

		while(modelContent.hasNext()){
			EObject eo= modelContent.next();

			if(eo instanceof Config){
				Config cTmp= (Config)eo;

				if(config.getName().compareTo(cTmp.getName())==0){
					nbTimes++;
				}
			}
		}

		if(nbTimes > 1){
			String error= "config \""+config.getName()+"\" have to be uniq";
			error(error, SDMakerV1Package.Literals.CONFIG__NAME);
		}
	}

	@Check
	public void checkGoodEval(Eval eval) throws IOException{

		SDMakerIO sdmakerIO= new SDMakerIO();


		//CHECK DIAGRAM REF
		try {
			sdmakerIO.findDiagramWithImport(eval.getDiagRef());
		} catch (RefConflictException | RefNotFoundException e) {
			error(e.getMessage(), SDMakerV1Package.Literals.EVAL__DIAG_REF);
		}



		//CHECK CONFIGS REFS
		for(Reference configRef : eval.getConfigRefs()){
			try {
				sdmakerIO.findConfigWithImports(eval.eResource(), configRef);
			} catch (RefConflictException | RefNotFoundException e) {
				error(e.getMessage(), 
						SDMakerV1Package.Literals.EVAL__CONFIG_REFS);
			}
		}
	}

	@Check
	public void checkGoodDiagramRef(DiagramRef diagRef) throws IOException{


		SDMakerIO sdmakerIO= new SDMakerIO();

		String strError= null;

		/* We will need to compute the diagram from which the reference is done 
		 * and the  referenced diagram for check if bindings between 
		 * them are good. */
		Diagram referencedDiag= null;
		Diagram currentDiag= 
				(Diagram)SDMakerV1Utils
				.findContainer(diagRef, Diagram.class);

		/* For code clarity, we will need to stock Lifelines that are
		 * related in a binding. . To recall, bindings have a sense: 
		 * currentDiag->referencedDiag. Names we choose reflect it. */
		Lifeline from= null;
		Lifeline to= null;

		/* For code clarity, if after checking we see that "from" and "to" 
		 * are both Obj Lifeline, we will need to stock them casted . */
		Obj objFrom= null;
		Obj objTo= null;

		/* For code clarity, we will need to stock Class refered by "from" and
		 * "to" Obj Lifeline. Be careful, it has nothing to do with 
		 * from.getClass() and to.getClass(). */
		String fromTypeStr= null;
		String toTypeStr=  null;

		/* When checking declared bindings, we going to put seen lifelines 
		 * names here. namesFrom corresponds to names in currentDiag when 
		 * namesTo corresponds to names in referencedDiag. If we stock a 
		 * name more than once,  that's an bindings-multi-usage-error. */
		List<String> namesFrom= new ArrayList<String>();
		List<String> namesTo= new ArrayList<String>();


		

		
		
		try {
			referencedDiag= sdmakerIO.findDiagramWithImport(diagRef.getRef());
		} catch (RefConflictException | RefNotFoundException e) {
			error(e.getMessage(), diagRef.getRef(),
					SDMakerV1Package.Literals.REFERENCE__NAME, -1);
		}

		
		
		


		/* Now, we going to check that all declared bindings are good. 
		 * Points to check are:
		 * 1. Lifelines designed by the binding exits.
		 * 2. They have the same kind (obj/obj) or (act/act)
		 * 3. If they are Obj, they have the same Type. 
		 * 4. We don't use a lifeline more than once. */
		for(Binding b : diagRef.getBindings()){

			//1.
			from= SDMakerV1Utils.findLifeline(currentDiag, b.getFrom());
			to= SDMakerV1Utils.findLifeline(referencedDiag, b.getTo());

			if(from==null){
				strError= b.getFrom()+" does not exist in current diagram.";
				error(strError, b, SDMakerV1Package.Literals.BINDING__FROM, -1);
			}
			if(to==null){
				strError= b.getTo()+" does not exist in referenced diagram.";
				error(strError, b, SDMakerV1Package.Literals.BINDING__TO, -1);
			}

			
			//2.
			if(from.getKind().compareTo(to.getKind())!=0){
				strError= b.getFrom()+" and "+ b.getTo();
				strError += " have to match the same kind: ";
				strError += "(act/act) or (obj/obj).";
				error(strError, b, SDMakerV1Package.Literals.BINDING__FROM, -1);
			}

			
			//3.
			if(from instanceof Obj){

				/*
				 * Because they have the same kind, we can 
				 * directly cast "to" without checking. */
				objFrom= (Obj)from;
				objTo= (Obj)to;


				fromTypeStr= 
						SDMakerV1Utils.findMatchingQualifiedNames(
								objFrom.eResource(),
								objFrom.getType().getIdentifier()).get(0);
				toTypeStr= 
						SDMakerV1Utils.findMatchingQualifiedNames(
								objTo.eResource(),
								objTo.getType().getIdentifier()).get(0);
				if(fromTypeStr.compareTo(toTypeStr) != 0){
					strError= b.getFrom()+" and "+ b.getTo();
					strError += " have to match the same type. ";
					error(strError, b, SDMakerV1Package.Literals.BINDING__FROM, -1);
				}
			}

			
			//4.
			if(SDMakerV1Utils.contains(namesFrom, b.getFrom())){

				strError= b.getFrom()+" cannot be used multiples times.";
				error(strError, b, SDMakerV1Package.Literals.BINDING__FROM, -1);
			}
			if(SDMakerV1Utils.contains(namesTo, b.getTo())){
				strError= b.getTo()+" cannot be used multiples times.";
				error(strError, b, SDMakerV1Package.Literals.BINDING__TO, -1);
			}

			namesFrom.add(b.getFrom());
			namesTo.add(b.getTo());
		}



		/* Now, we going to check if no bindings have been forgot. 
		 * For that, we going to compute all Lifeline in referencedDiag and
		 * check that all of them have its name in namesTo list. */
		for(Lifeline l : SDMakerV1Utils.getAll(
				referencedDiag.eContents(), 
				Lifeline.class)){
			if(SDMakerV1Utils.contains(namesTo, l.getName())==false){
				strError= l.getName()+" from referenced diagram is not binded.";
				error(strError, SDMakerV1Package.Literals.DIAGRAM_REF__REF);
			}
		}
	}
}
